import { parse, resolve } from 'path'
import { appendFileSync, existsSync, lstatSync, mkdirSync, readdirSync, rmdirSync, unlinkSync } from 'fs'
import { EOL } from 'os'
import { compile, Options } from 'json-schema-to-typescript'
import type { JSONSchema4 } from 'json-schema'
import { Recordable } from './types'

const JSTTOptions: Partial<Options> = {
  bannerComment: '',
  style: {
    bracketSpacing: false,
    printWidth: 120,
    semi: false,
    singleQuote: true,
    tabWidth: 2,
    trailingComma: 'none',
    useTabs: false
  }
}

/**
 * 处理jsonschema
 * @param jsonSchema
 */
export function processJsonSchema<T extends JSONSchema4>(jsonSchema: T): T {
  /* istanbul ignore if */
  if (typeof jsonSchema !== 'object') { return jsonSchema }

  // 去除 title 和 id，防止 json-schema-to-typescript 提取它们作为接口名
  delete jsonSchema.title
  delete jsonSchema.i

  // 忽略数组长度限制
  delete jsonSchema.minItems
  delete jsonSchema.maxItems

  // 将 additionalProperties 设为 false
  // 避免生成[k: string]: any | unknown
  jsonSchema.additionalProperties = false

  // 删除ref
  delete jsonSchema.$ref
  delete jsonSchema.$$ref

  // 移除字段名称首尾空格
  if (jsonSchema.properties) {
    Object.keys(jsonSchema.properties).forEach((prop) => {
      const propDef = jsonSchema.properties![prop]
      delete jsonSchema.properties![prop]
      jsonSchema.properties![(prop as string).trim()] = propDef
      processJsonSchema(propDef)
    })
    if (jsonSchema.required && Array.isArray(jsonSchema.required)) {
      jsonSchema.required = jsonSchema.required.map(prop => prop.trim())
    }
  }

  // 继续处理数组的子元素
  if (jsonSchema.items) {
    const itemsArr = Array.isArray(jsonSchema.items) ? (jsonSchema.items) : [jsonSchema.items]
    itemsArr.forEach(processJsonSchema)
  }

  // 处理 oneOf
  if (jsonSchema.oneOf) {
    jsonSchema.oneOf.forEach(processJsonSchema)
  }

  // 处理 anyOf
  if (jsonSchema.anyOf) {
    jsonSchema.anyOf.forEach(processJsonSchema)
  }

  // 处理 allOf
  if (jsonSchema.allOf) {
    jsonSchema.allOf.forEach(processJsonSchema)
  }

  return jsonSchema
}

/**
 * 对象转参数字符串
 * @param obj
 */
export const obj2SearchString = (obj: Recordable) => {
  const arr = Object.keys(obj).map(key => ([key, obj[key]]))
  return new URLSearchParams(arr).toString()
}

/**
 * 将 jsonschema 写入文件
 * @param schema
 * @param filePath
 * @param title
 */
export async function writeToFile(schema: JSONSchema4, filePath: string, title: string) {
  schema = processJsonSchema(schema)
  schema.title = title
  const compiled = await compile(schema, schema.title, JSTTOptions)
  writeFile(filePath, compiled)
}

/**
 * 文件不存在则创建
 * @param dir
 */
export function dirExistsOrCreate(dir: string) {
  const parsedPath = parse(dir).dir
  if (!existsSync(parsedPath)) {
    mkdirSync(parsedPath, { recursive: true })
  }
}

/**
 * 写入文件
 * @param dir
 * @param data
 */
export function writeFile(dir: string, data: string) {
  dirExistsOrCreate(dir)
  appendFileSync(dir, `${EOL}${data}`)
}

/**
 * 清空目录
 * @param dir 目录地址
 */
export function emptyDir(dir: string) {
  if (!existsSync(dir)) {
    return
  }
  for (const file of readdirSync(dir)) {
    const abs = resolve(dir, file)
    // baseline is Node 12 so can't use rmSync :(
    if (lstatSync(abs).isDirectory()) {
      emptyDir(abs)
      rmdirSync(abs)
    } else {
      unlinkSync(abs)
    }
  }
}
